using System;
using System.IO;

using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Cms;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;

namespace Org.BouncyCastle.Cms
{
	/**
	 * General class for generating a CMS authenticated-data message.
	 *
	 * A simple example of usage.
	 *
	 * <pre>
	 *      CMSAuthenticatedDataGenerator  fact = new CMSAuthenticatedDataGenerator();
	 *
	 *      fact.addKeyTransRecipient(cert);
	 *
	 *      CMSAuthenticatedData         data = fact.generate(content, algorithm, "BC");
	 * </pre>
	 */
	public class CmsAuthenticatedDataGenerator
	    : CmsAuthenticatedGenerator
	{
	    /**
	     * base constructor
	     */
	    public CmsAuthenticatedDataGenerator()
	    {
	    }

	    /**
	     * constructor allowing specific source of randomness
	     * @param rand instance of SecureRandom to use
	     */
	    public CmsAuthenticatedDataGenerator(
	        SecureRandom rand)
	        : base(rand)
	    {
	    }

	    /**
	     * generate an enveloped object that contains an CMS Enveloped Data
	     * object using the given provider and the passed in key generator.
	     */
	    private CmsAuthenticatedData Generate(
	        CmsProcessable		content,
	        string				macOid,
	        string				dataOid,
	        CipherKeyGenerator	keyGen)
	    {
	        Asn1EncodableVector     recipientInfos = new Asn1EncodableVector();
	        AlgorithmIdentifier     macAlgId;
	        SecretKey               encKey;
	        Asn1OctetString         encContent;
	        Asn1OctetString         macResult;

	        try
	        {
	            IMac mac = CmsEnvelopedHelper.Instance.GetMac(macOid);

	            encKey = keyGen.generateKey();
	            AlgorithmParameterSpec parameters = generateParameterSpec(macOID, encKey);

	            mac.Init(encKey, parameters);

	            macAlgId = getAlgorithmIdentifier(macOID, parameters);

	            MemoryStream bOut = new MemoryStream();
	            MacOutputStream mOut = new MacOutputStream(bOut, mac);

	            content.Write(mOut);

	            mOut.Close();
	            bOut.Close();

	            encContent = new BerOctetString(bOut.ToArray());
	            macResult = new DerOctetString(mOut.GetMac());
	        }
	        catch (InvalidKeyException e)
	        {
	            throw new CmsException("key invalid in message.", e);
	        }
	        catch (NoSuchPaddingException e)
	        {
	            throw new CmsException("required padding not supported.", e);
	        }
	        catch (InvalidAlgorithmParameterException e)
	        {
	            throw new CmsException("algorithm parameters invalid.", e);
	        }
	        catch (IOException e)
	        {
	            throw new CmsException("exception decoding algorithm parameters.", e);
	        }
	        catch (InvalidParameterSpecException e)
	        {
	           throw new CmsException("exception setting up parameters.", e);
	        }

			foreach (RecipientInf recipient in recipientInfs) 
	        {
	            try
	            {
	                recipientInfos.Add(recipient.ToRecipientInfo(encKey, rand));
	            }
	            catch (IOException e)
	            {
	                throw new CmsException("encoding error.", e);
	            }
	            catch (InvalidKeyException e)
	            {
	                throw new CmsException("key inappropriate for algorithm.", e);
	            }
	            catch (GeneralSecurityException e)
	            {
	                throw new CmsException("error making encrypted content.", e);
	            }
	        }

	        ContentInfo eci = new ContentInfo(new DerObjectIdentifier(dataOID), encContent);
	        ContentInfo contentInfo = new ContentInfo(
	        	PkcsObjectIdentifiers.IdCTAuthData,
				new AuthenticatedData(null, new DerSet(recipientInfos), macAlgId, null, eci, null, macResult, null));

	        return new CmsAuthenticatedData(contentInfo);
	    }

	    /**
	     * generate an authenticated object that contains an CMS Authenticated Data object
	     */
	    public CmsAuthenticatedData Generate(
	        CmsProcessable	content,
	        string			encryptionOid)
	    {
	        KeyGenerator keyGen = CmsEnvelopedHelper.Instance.createSymmetricKeyGenerator(encryptionOid);

	        keyGen.Init(rand);

	        return Generate(content, encryptionOid, PkcsObjectIdentifiers.Data.Id, keyGen);
	    }
	}
}
